﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ChartEdit
{
    public class OffsetTransformer //Going to be used to draw the chart properly with BPM changes
    {
        public float songLength; //default song length of 10 seconds for debug purposes
        public List<int> Offset = new List<int>();
        public List<float> BPM = new List<float>();
        public List<float> timeOffset = new List<float>();

        private Chart chart;

        public void CreateOffsets()
        {
            timeOffset.Clear();
            timeOffset.Add(0);
            for (int i = 0; i < Offset.Count - 1; i++)
            {
                //                                    Get the amount of units  | Convert to beats | Convert to seconds
                //                                    between offsets
                timeOffset.Add(timeOffset[i] + (float)(Offset[i + 1] - Offset[i]) / chart.Resolution / BPM[i] * 60f);
            }
        }

        public int PrevBPMIndex(int offset)
        {
            int a = 0;
            for (a = 0; a < Offset.Count; a++)
            {
                if (Offset[a] >= offset)
                    break;
            }
            a--;
            return a;
        }
        public int NextBPMIndex(int offset)
        {
            int a = 0;
            for (a = 0; a < Offset.Count; a++)
            {
                if (Offset[a] >= offset)
                    break;
            }
            return a;
        }

        public int GetOffset(float CrudeOffset)
        {
            return GetOffset(CrudeOffset, 0f);
        }
        public int GetOffset(float CrudeOffset, float extraSeconds)
        {
            float target = (CrudeOffset) + extraSeconds;
            int a = 0;
            for (a = 0; a < Offset.Count; a++)
            {
                if (timeOffset[a] > target)
                    break;
            }
            a--;
            if (a < 0) a = 0;
            return Offset[a] + (int)((target - timeOffset[a]) / 60f * BPM[a] * chart.Resolution);
        }

        //This shit is magic
        public float GetTime(int Offset)
        {
            int a = 0;
            for (a = 0; a < this.Offset.Count; a++)
            {
                if (this.Offset[a] > Offset)
                    break;
            }
            if (a > 0) a--;
            return timeOffset[a] + (float)(Offset - this.Offset[a]) / chart.Resolution / BPM[a] * 60f;
        }


        #region Nearest Note Functions
        public int nearNIndex(int Offset, NoteTrack Notes)
        {
            if (Notes == null)
                return 0;
            int min = 0;
            int max = Notes.Count() - 1;

            switch (max)
            {
                case (-1):
                case (0):
                    return 0;
                case (1):
                    {
                        int d1 = Math.Abs(Notes[0].Offset - Offset);
                        int d2 = Math.Abs(Notes[1].Offset - Offset);
                        if (d1 < d2)
                            return 0;
                        return 1;
                    }
                default:
                    {

                        int index = max / 2;

                        while (Notes[index].Offset != Offset)
                        {
                            index = (min + max) / 2;
                            if (Notes[index].Offset > Offset)
                                max = index;
                            else
                                min = index;

                            if (max - min <= 1)
                            {
                                int d1 = Math.Abs(Notes[index].Offset - Offset);
                                int d2 = Math.Abs(Notes[index - 1].Offset - Offset);
                                int d3 = Math.Abs(Notes[index + 1].Offset - Offset);
                                if (d1 <= d2 && d1 <= d3)
                                    return index;
                                if (d2 <= d3)
                                    return index - 1;
                                return index + 1;
                            }
                        }
                        return index;
                    }
            }
        }
        public int nearNIndex(int Offset, List<Note> Notes)
        {
            if (Notes == null)
                return 0;
            int min = 0;
            int max = Notes.Count() - 1;

            switch (max)
            {
                case (-1):
                case (0):
                    return 0;
                case (1):
                    {
                        int d1 = Math.Abs(Notes[0].Offset - Offset);
                        int d2 = Math.Abs(Notes[1].Offset - Offset);
                        if (d1 < d2)
                            return 0;
                        return 1;
                    }
                default:
                    {

                        int index = max / 2;

                        while (Notes[index].Offset != Offset)
                        {
                            index = (min + max) / 2;
                            if (Notes[index].Offset > Offset)
                                max = index;
                            else
                                min = index;

                            if (max - min <= 1)
                            {
                                int d1 = Math.Abs(Notes[index].Offset - Offset);
                                int d2 = Math.Abs(Notes[index - 1].Offset - Offset);
                                int d3 = Math.Abs(Notes[index + 1].Offset - Offset);
                                if (d1 <= d2 && d1 <= d3)
                                    return index;
                                if (d2 <= d3)
                                    return index - 1;
                                return index + 1;
                            }
                        }
                        return index;
                    }
            }
        }
        public int nearNIndex(int Offset, NoteTrack Notes, int fret)
        {
            if (Notes == null)
                return 0;
            int min = 0;
            int max = Notes.Count(fret) - 1;

            switch (max)
            {
                case(-1):
                case(0):
                    return 0;
                case (1):
                    {
                        int d1 = Math.Abs(Notes[0,fret].Offset - Offset);
                        int d2 = Math.Abs(Notes[1,fret].Offset - Offset);
                        if (d1 < d2)
                            return 0;
                        return 1;
                    }
                    
                default:
                    {
                        int index = max / 2;

                        while (Notes[index, fret].Offset != Offset)
                        {
                            index = (min + max) / 2;
                            if (Notes[index, fret].Offset > Offset)
                                max = index;
                            else
                                min = index;

                            if (max - min <= 1)
                            {
                                int d1 = Math.Abs(Notes[index].Offset - Offset);
                                int d2 = Math.Abs(Notes[index - 1].Offset - Offset);
                                int d3 = Math.Abs(Notes[index + 1].Offset - Offset);
                                if (d1 <= d2 && d1 <= d3)
                                    return index;
                                if (d2 <= d3)
                                    return index - 1;
                                return index + 1;
                            }
                        }
                        return index;
                    }
            }
        }
        public int prevNIndex(int Offset, NoteTrack Notes)
        {
            if (Notes == null)
                return 0;
            int i;
            for (i = nearNIndex(Offset, Notes); i >= 0; i--)
            {
                if (Notes[i].Offset < Offset)
                    return i;
            }
            return 0;
        }
        public int prevNIndex(int Offset, List<Note> Notes)
        {
            if (Notes == null)
                return 0;
            int i;
            for (i = nearNIndex(Offset, Notes); i >= 0; i--)
            {
                if (Notes[i].Offset < Offset)
                    return i;
            }
            return 0;
        }
        public int nextNIndex(int Offset, NoteTrack Notes)
        {
            if (Notes == null)
                return 0;
            int i;
            for (i = nearNIndex(Offset, Notes); i < Notes.Count(); i++)
            {
                if (Notes[i].Offset > Offset)
                    return i;
            }
            return i-1;
        }
        public int nextNIndex(int Offset, List<Note> Notes)
        {
            if (Notes == null)
                return 0;
            int i;
            for (i = nearNIndex(Offset, Notes); i < Notes.Count(); i++)
            {
                if (Notes[i].Offset > Offset)
                    return i;
            }
            return i - 1;
        }
        
        public int nearNIndexEnd(int Offset, NoteTrack Notes)
        {
            if (Notes == null)
                return 0;
            int min = 0;
            int max = Notes.Count() - 1;

            switch (max)
            {
                case (-1):
                case (0):
                    return 0;
                case (1):
                    {
                        int d1 = Math.Abs(Notes[0].OffsetEnd - Offset);
                        int d2 = Math.Abs(Notes[1].OffsetEnd - Offset);
                        if (d1 < d2)
                            return 0;
                        return 1;
                    }
                default:
                    {

                        int index = max / 2;

                        while (Notes[index].OffsetEnd != Offset)
                        {
                            index = (min + max) / 2;
                            if (Notes[index].OffsetEnd > Offset)
                                max = index;
                            else
                                min = index;

                            if (max - min <= 1)
                            {
                                int d1 = Math.Abs(Notes[index].OffsetEnd - Offset);
                                int d2 = Math.Abs(Notes[index - 1].OffsetEnd - Offset);
                                int d3 = Math.Abs(Notes[index + 1].OffsetEnd - Offset);
                                if (d1 <= d2 && d1 <= d3)
                                    return index;
                                if (d2 <= d3)
                                    return index - 1;
                                return index + 1;
                            }
                        }
                        return index;
                    }
            }
        }
        public int nearNIndexEnd(int Offset, NoteTrack Notes, int fret)
        {
            if (Notes == null)
                return 0;
            int min = 0;
            int max = Notes.Count(fret) - 1;

            switch (max)
            {
                case (-1):
                case (0):
                    return 0;
                case (1):
                    {
                        int d1 = Math.Abs(Notes[0,fret].OffsetEnd - Offset);
                        int d2 = Math.Abs(Notes[1,fret].OffsetEnd - Offset);
                        if (d1 < d2)
                            return 0;
                        return 1;
                    }
                default:
                    {

                        int index = max / 2;

                        while (Notes[index,fret].OffsetEnd != Offset)
                        {
                            index = (min + max) / 2;
                            if (Notes[index, fret].OffsetEnd > Offset)
                                max = index;
                            else
                                min = index;

                            if (max - min <= 1)
                            {
                                int d1 = Math.Abs(Notes[index, fret].OffsetEnd - Offset);
                                int d2 = Math.Abs(Notes[index - 1, fret].OffsetEnd - Offset);
                                int d3 = Math.Abs(Notes[index + 1, fret].OffsetEnd - Offset);
                                if (d1 <= d2 && d1 <= d3)
                                    return index;
                                if (d2 <= d3)
                                    return index - 1;
                                return index + 1;
                            }
                        }
                        return index;
                    }
            }
        } 
        #endregion

        public OffsetTransformer(Chart chart)
        {
            this.chart = chart;
            BPM.Clear();
            Offset.Clear();
            timeOffset.Clear();
            foreach (SyncTrackEntry sect in chart.SyncTrack)
            {
                if (sect.BPM != 0)
                {
                    BPM.Add(((float)sect.BPM) / 1000f);
                    Offset.Add(sect.Offset);
                }
            }
            CreateOffsets();
            songLength = GetTime(chart.LastIndex);
        }
    }

}
